/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.xml;

import java.beans.ConstructorProperties;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.MethodReplacer;
import org.springframework.tests.sample.beans.ITestBean;
import org.springframework.tests.sample.beans.IndexedTestBean;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.tests.sample.beans.factory.DummyFactory;

/**
 * Types used by {@link XmlBeanFactoryTests} and its attendant XML config files.
 *
 * @author Chris Beams
 */
final class XmlBeanFactoryTestTypes {
}


/**
 * Simple bean used to check constructor dependency checking.
 *
 * @author Juergen Hoeller
 * @since 09.11.2003
 */
@SuppressWarnings("serial")
class ConstructorDependenciesBean implements Serializable {

    private int age;

    private String name;

    private TestBean spouse1;

    private TestBean spouse2;

    private IndexedTestBean other;

    public ConstructorDependenciesBean(int age) {
        this.age = age;
    }

    public ConstructorDependenciesBean(String name) {
        this.name = name;
    }

    public ConstructorDependenciesBean(TestBean spouse1) {
        this.spouse1 = spouse1;
    }

    public ConstructorDependenciesBean(TestBean spouse1, TestBean spouse2) {
        this.spouse1 = spouse1;
        this.spouse2 = spouse2;
    }

    @ConstructorProperties({"spouse", "otherSpouse", "myAge"})
    public ConstructorDependenciesBean(TestBean spouse1, TestBean spouse2, int age) {
        this.spouse1 = spouse1;
        this.spouse2 = spouse2;
        this.age = age;
    }

    public ConstructorDependenciesBean(TestBean spouse1, TestBean spouse2, IndexedTestBean other) {
        this.spouse1 = spouse1;
        this.spouse2 = spouse2;
        this.other = other;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public TestBean getSpouse1() {
        return spouse1;
    }

    public TestBean getSpouse2() {
        return spouse2;
    }

    public IndexedTestBean getOther() {
        return other;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
}


class SimpleConstructorArgBean {

    private int age;

    private String name;

    public SimpleConstructorArgBean() {
    }

    public SimpleConstructorArgBean(int age) {
        this.age = age;
    }

    public SimpleConstructorArgBean(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }
}


/**
 * Bean testing the ability to use both lookup method overrides
 * and constructor injection.
 * There is also a property ("setterString") to be set via
 * Setter Injection.
 *
 * @author Rod Johnson
 */
abstract class ConstructorInjectedOverrides {

    private ITestBean tb;

    private String setterString;

    public ConstructorInjectedOverrides(ITestBean tb) {
        this.tb = tb;
    }

    public ITestBean getTestBean() {
        return this.tb;
    }

    protected abstract FactoryMethods createFactoryMethods();

    public String getSetterString() {
        return setterString;
    }

    public void setSetterString(String setterString) {
        this.setterString = setterString;
    }
}


/**
 * Simple bean used to check constructor dependency checking.
 *
 * @author Juergen Hoeller
 * @since 09.11.2003
 */
@SuppressWarnings({"serial", "unused"})
class DerivedConstructorDependenciesBean extends ConstructorDependenciesBean {

    boolean initialized;
    boolean destroyed;

    DerivedConstructorDependenciesBean(TestBean spouse1, TestBean spouse2, IndexedTestBean other) {
        super(spouse1, spouse2, other);
    }

    private DerivedConstructorDependenciesBean(TestBean spouse1, Object spouse2, IndexedTestBean other) {
        super(spouse1, null, other);
    }

    protected DerivedConstructorDependenciesBean(TestBean spouse1, TestBean spouse2, IndexedTestBean other, int age, int otherAge) {
        super(spouse1, spouse2, other);
    }

    public DerivedConstructorDependenciesBean(TestBean spouse1, TestBean spouse2, IndexedTestBean other, int age, String name) {
        super(spouse1, spouse2, other);
        setAge(age);
        setName(name);
    }

    private void init() {
        this.initialized = true;
    }

    private void destroy() {
        this.destroyed = true;
    }
}


/**
 * @author Rod Johnson
 */
interface DummyBo {

    void something();
}


/**
 * @author Rod Johnson
 */
class DummyBoImpl implements DummyBo {

    DummyDao dao;

    public DummyBoImpl(DummyDao dao) {
        this.dao = dao;
    }

    @Override
    public void something() {
    }
}


/**
 * @author Rod Johnson
 */
class DummyDao {
}


/**
 * @author Juergen Hoeller
 * @since 21.07.2003
 */
class DummyReferencer {

    private TestBean testBean1;

    private TestBean testBean2;

    private DummyFactory dummyFactory;

    public DummyReferencer() {
    }

    public DummyReferencer(DummyFactory dummyFactory) {
        this.dummyFactory = dummyFactory;
    }

    public void setDummyFactory(DummyFactory dummyFactory) {
        this.dummyFactory = dummyFactory;
    }

    public DummyFactory getDummyFactory() {
        return dummyFactory;
    }

    public void setTestBean1(TestBean testBean1) {
        this.testBean1 = testBean1;
    }

    public TestBean getTestBean1() {
        return testBean1;
    }

    public void setTestBean2(TestBean testBean2) {
        this.testBean2 = testBean2;
    }

    public TestBean getTestBean2() {
        return testBean2;
    }
}


/**
 * Test class for Spring's ability to create objects using static
 * factory methods, rather than constructors.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
@SuppressWarnings("unused")
class FactoryMethods {

    public static FactoryMethods nullInstance() {
        return null;
    }

    public static FactoryMethods defaultInstance() {
        TestBean tb = new TestBean();
        tb.setName("defaultInstance");
        return new FactoryMethods(tb, "default", 0);
    }

    /**
     * Note that overloaded methods are supported.
     */
    public static FactoryMethods newInstance(TestBean tb) {
        return new FactoryMethods(tb, "default", 0);
    }

    protected static FactoryMethods newInstance(TestBean tb, int num, String name) {
        if (name == null) {
            throw new IllegalStateException("Should never be called with null value");
        }
        return new FactoryMethods(tb, name, num);
    }

    static FactoryMethods newInstance(TestBean tb, int num, Integer something) {
        if (something != null) {
            throw new IllegalStateException("Should never be called with non-null value");
        }
        return new FactoryMethods(tb, null, num);
    }

    private static List<?> listInstance() {
        return Collections.EMPTY_LIST;
    }

    private int num = 0;
    private String name = "default";
    private TestBean tb;
    private String stringValue;

    /**
     * Constructor is private: not for use outside this class,
     * even by IoC container.
     */
    private FactoryMethods(TestBean tb, String name, int num) {
        this.tb = tb;
        this.name = name;
        this.num = num;
    }

    public void setStringValue(String stringValue) {
        this.stringValue = stringValue;
    }

    public String getStringValue() {
        return this.stringValue;
    }

    public TestBean getTestBean() {
        return this.tb;
    }

    protected TestBean protectedGetTestBean() {
        return this.tb;
    }

    private TestBean privateGetTestBean() {
        return this.tb;
    }

    public int getNum() {
        return num;
    }

    public String getName() {
        return name;
    }

    /**
     * Set via Setter Injection once instance is created.
     */
    public void setName(String name) {
        this.name = name;
    }
}

/**
 * Fixed method replacer for String return types
 *
 * @author Rod Johnson
 */
class FixedMethodReplacer implements MethodReplacer {

    public static final String VALUE = "fixedMethodReplacer";

    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        return VALUE;
    }
}


/**
 * @author Chris Beams
 */
class MapAndSet {

    private Object obj;

    public MapAndSet(Map<?, ?> map) {
        this.obj = map;
    }

    public MapAndSet(Set<?> set) {
        this.obj = set;
    }

    public Object getObject() {
        return obj;
    }
}


/**
 * @author Rod Johnson
 */
class MethodReplaceCandidate {

    public String replaceMe(String echo) {
        return echo;
    }
}


/**
 * Bean that exposes a simple property that can be set
 * to a mix of references and individual values.
 */
class MixedCollectionBean {

    private Collection<?> jumble;

    public void setJumble(Collection<?> jumble) {
        this.jumble = jumble;
    }

    public Collection<?> getJumble() {
        return jumble;
    }
}


/**
 * @author Juergen Hoeller
 */
interface OverrideInterface {

    TestBean getPrototypeDependency();

    TestBean getPrototypeDependency(Object someParam);
}


/**
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
abstract class OverrideOneMethod extends MethodReplaceCandidate implements OverrideInterface {

    protected abstract TestBean protectedOverrideSingleton();

    @Override
    public TestBean getPrototypeDependency(Object someParam) {
        return new TestBean();
    }

    public TestBean invokesOverriddenMethodOnSelf() {
        return getPrototypeDependency();
    }

    public String echo(String echo) {
        return echo;
    }

    /**
     * Overloaded form of replaceMe.
     */
    public String replaceMe() {
        return "replaceMe";
    }

    /**
     * Another overloaded form of replaceMe, not getting replaced.
     * Must not cause errors when the other replaceMe methods get replaced.
     */
    public String replaceMe(int someParam) {
        return "replaceMe:" + someParam;
    }

    @Override
    public String replaceMe(String someParam) {
        return "replaceMe:" + someParam;
    }
}


/**
 * Subclass of OverrideOneMethod, to check that overriding is
 * supported for inherited methods.
 *
 * @author Rod Johnson
 */
abstract class OverrideOneMethodSubclass extends OverrideOneMethod {

    protected void doSomething(String arg) {
        // This implementation does nothing!
        // It's not overloaded
    }
}


/**
 * Simple test of BeanFactory initialization and lifecycle callbacks.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
class ProtectedLifecycleBean implements BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean {

    protected boolean initMethodDeclared = false;

    protected String beanName;

    protected BeanFactory owningFactory;

    protected boolean postProcessedBeforeInit;

    protected boolean inited;

    protected boolean initedViaDeclaredInitMethod;

    protected boolean postProcessedAfterInit;

    protected boolean destroyed;

    public void setInitMethodDeclared(boolean initMethodDeclared) {
        this.initMethodDeclared = initMethodDeclared;
    }

    public boolean isInitMethodDeclared() {
        return initMethodDeclared;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    public String getBeanName() {
        return beanName;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.owningFactory = beanFactory;
    }

    public void postProcessBeforeInit() {
        if (this.inited || this.initedViaDeclaredInitMethod) {
            throw new RuntimeException("Factory called postProcessBeforeInit after afterPropertiesSet");
        }
        if (this.postProcessedBeforeInit) {
            throw new RuntimeException("Factory called postProcessBeforeInit twice");
        }
        this.postProcessedBeforeInit = true;
    }

    @Override
    public void afterPropertiesSet() {
        if (this.owningFactory == null) {
            throw new RuntimeException("Factory didn't call setBeanFactory before afterPropertiesSet on lifecycle bean");
        }
        if (!this.postProcessedBeforeInit) {
            throw new RuntimeException("Factory didn't call postProcessBeforeInit before afterPropertiesSet on lifecycle bean");
        }
        if (this.initedViaDeclaredInitMethod) {
            throw new RuntimeException("Factory initialized via declared init method before initializing via afterPropertiesSet");
        }
        if (this.inited) {
            throw new RuntimeException("Factory called afterPropertiesSet twice");
        }
        this.inited = true;
    }

    public void declaredInitMethod() {
        if (!this.inited) {
            throw new RuntimeException("Factory didn't call afterPropertiesSet before declared init method");
        }

        if (this.initedViaDeclaredInitMethod) {
            throw new RuntimeException("Factory called declared init method twice");
        }
        this.initedViaDeclaredInitMethod = true;
    }

    public void postProcessAfterInit() {
        if (!this.inited) {
            throw new RuntimeException("Factory called postProcessAfterInit before afterPropertiesSet");
        }
        if (this.initMethodDeclared && !this.initedViaDeclaredInitMethod) {
            throw new RuntimeException("Factory called postProcessAfterInit before calling declared init method");
        }
        if (this.postProcessedAfterInit) {
            throw new RuntimeException("Factory called postProcessAfterInit twice");
        }
        this.postProcessedAfterInit = true;
    }

    /**
     * Dummy business method that will fail unless the factory
     * managed the bean's lifecycle correctly
     */
    public void businessMethod() {
        if (!this.inited || (this.initMethodDeclared && !this.initedViaDeclaredInitMethod) ||
                !this.postProcessedAfterInit) {
            throw new RuntimeException("Factory didn't initialize lifecycle object correctly");
        }
    }

    @Override
    public void destroy() {
        if (this.destroyed) {
            throw new IllegalStateException("Already destroyed");
        }
        this.destroyed = true;
    }

    public boolean isDestroyed() {
        return destroyed;
    }


    public static class PostProcessor implements BeanPostProcessor {

        @Override
        public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
            if (bean instanceof ProtectedLifecycleBean) {
                ((ProtectedLifecycleBean) bean).postProcessBeforeInit();
            }
            return bean;
        }

        @Override
        public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
            if (bean instanceof ProtectedLifecycleBean) {
                ((ProtectedLifecycleBean) bean).postProcessAfterInit();
            }
            return bean;
        }
    }
}


/**
 * @author Rod Johnson
 */
@SuppressWarnings("serial")
class ReverseMethodReplacer implements MethodReplacer, Serializable {

    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        String s = (String) args[0];
        return new StringBuffer(s).reverse().toString();
    }
}


/**
 * @author Rod Johnson
 */
@SuppressWarnings("serial")
abstract class SerializableMethodReplacerCandidate extends MethodReplaceCandidate implements Serializable {

    //public abstract Point getPoint();
}


/**
 * @author Juergen Hoeller
 * @since 23.10.2004
 */
class SingleSimpleTypeConstructorBean {

    private boolean singleBoolean;

    private boolean secondBoolean;

    private String testString;

    public SingleSimpleTypeConstructorBean(boolean singleBoolean) {
        this.singleBoolean = singleBoolean;
    }

    protected SingleSimpleTypeConstructorBean(String testString, boolean secondBoolean) {
        this.testString = testString;
        this.secondBoolean = secondBoolean;
    }

    public boolean isSingleBoolean() {
        return singleBoolean;
    }

    public boolean isSecondBoolean() {
        return secondBoolean;
    }

    public String getTestString() {
        return testString;
    }
}
